package com.gangwangchina.android.contactlib;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.gangwangchina.android.contactlib.iterators.PersonContactIterator;
import com.gangwangchina.android.contactlib.iterators.PersonIterator;
import com.gangwangchina.android.contactlib.model.DataElement;
import com.gangwangchina.android.contactlib.model.EmailAddress;
import com.gangwangchina.android.contactlib.model.IMAddress;
import com.gangwangchina.android.contactlib.model.Organization;
import com.gangwangchina.android.contactlib.model.Person;
import com.gangwangchina.android.contactlib.model.PersonContact;
import com.gangwangchina.android.contactlib.model.PostalAddress;
import com.gangwangchina.android.contactlib.model.TelephoneNumber;
import com.gangwangchina.android.contactlib.model.WebSite;
import com.gangwangchina.android.contactlib.utils.CursorUtils;

import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
import android.util.Log;



public class ContactsHelper implements ContactsInterface {

	private Context ctx;

	public static final String TAG = "ContactsHelper";

	public ContactsHelper(Context ctx) {
		this.ctx = ctx;
		
	}

	public boolean update(Person person) throws RemoteException,
			OperationApplicationException {
		Iterator<PersonContact> it = person.personContactsIterator();
		boolean success = true;
		PersonContact pc = null;
		while (it.hasNext())
			pc = it.next();
		boolean lastResult = (pc.id == NO_ID) ? add(pc) != INVALID_ID
				: update(pc);
		success = success && lastResult;
		Log.d("JUNIT", "Add ou update ID = " + pc.id);	
		return success;
	}

	public long add(PersonContact pc) throws RemoteException,
			OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
		ContentProviderOperation.Builder builder = ContentProviderOperation
				.newInsert(RawContacts.CONTENT_URI)
				.withValue(RawContacts.ACCOUNT_TYPE, pc.accountType)
				.withValue(RawContacts.ACCOUNT_NAME, pc.accountName);

		if (pc.personId != NO_ID)
			builder.withValue(RawContacts.CONTACT_ID, pc.personId);

		ops.add(builder.build());
		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);

		long id = (results.length == 1) ? ContentUris.parseId(results[0].uri)
				: INVALID_ID;
		pc.setNewId(id);

		pc.personId = getPersonContactById(id).personId;

		if( pc.name != null )
		{
			pc.name.id = add(pc.name);
		}
		for (Iterator<TelephoneNumber> it = pc.telephoneNumbers.iterator(); it
				.hasNext();) {
			TelephoneNumber tn = it.next();
			tn.id = add(tn);
		}

		for (Iterator<EmailAddress> it = pc.emailAddresses.iterator(); it
				.hasNext();) {
			EmailAddress ea = it.next();
			ea.id = add(ea);
		}

		for (Iterator<PostalAddress> it = pc.postalAddresses.iterator(); it
				.hasNext();) {
			PostalAddress pa = it.next();
			pa.id = add(pa);
		}

		for (Iterator<Organization> it = pc.organizations
				.iterator(); it.hasNext();) {
			Organization org = it.next();
			org.id = add(org);
		}
		
		if (pc.notes != null) {
			add(pc.notes);
		}
		
		if (pc.nickname != null) {
			add(pc.nickname);
		}
		
		for (Iterator<WebSite> it = pc.websites
				.iterator(); it.hasNext();) {
			WebSite website = it.next();
			website.id = add(website);
		}
		

		for (Iterator<IMAddress> it = pc.ims
				.iterator(); it.hasNext();) {
			IMAddress im = it.next();
			im.id = add(im);
		}
		
		if( pc.photo != null)
		{
			add(pc.photo);
		}

		return id;
	}

	public boolean update(PersonContact pc) throws RemoteException,
			OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

		ops.add(ContentProviderOperation
				.newUpdate(
						ContentUris.withAppendedId(RawContacts.CONTENT_URI,
								pc.id))
				.withValue(RawContacts.ACCOUNT_TYPE, pc.accountType)
				.withValue(RawContacts.ACCOUNT_NAME, pc.accountName).build());

		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);

		if (pc.name != null) {
			if (pc.name.id == NO_ID)
				add(pc.name);
			else
				update(pc.name);
		}
		for (Iterator<TelephoneNumber> it = pc.telephoneNumbers.iterator(); it
				.hasNext();) {
			TelephoneNumber tn = it.next();
			if (tn.id == NO_ID)
				add(tn);
			else
				update(tn);
		}
		for (Iterator<EmailAddress> it = pc.emailAddresses.iterator(); it
				.hasNext();) {
			EmailAddress ea = it.next();
			if (ea.id == NO_ID)
				add(ea);
			else
				update(ea);
		}
		for (Iterator<PostalAddress> it = pc.postalAddresses.iterator(); it
				.hasNext();) {
			PostalAddress pa = it.next();
			if (pa.id == NO_ID)
				add(pa);
			else
				update(pa);
		}

		for (Iterator<Organization> it = pc.organizations
				.iterator(); it.hasNext();) {
			Organization org = it.next();
			if (org.id == NO_ID)
				add(org);
			else
				update(org);
		}
		
		if (pc.notes != null) {
			if(pc.notes.id == NO_ID)
			{
				add(pc.notes);
			}
			else
			{
				update(pc.notes);
			}
			
		}
		
		if (pc.nickname != null) {
			if(pc.nickname.id == NO_ID)
			{
				add(pc.nickname);
			}
			else
			{
				update(pc.nickname);
			}
			
		}
		
		for (Iterator<WebSite> it = pc.websites
				.iterator(); it.hasNext();) {
			WebSite website = it.next();
			if (website.id == NO_ID)
				add(website);
			else
				update(website);
		}
		
		
		for (Iterator<IMAddress> it = pc.ims
				.iterator(); it.hasNext();) {
			IMAddress im = it.next();
			if (im.id == NO_ID)
				add(im);
			else
				update(im);
		}

		if (pc.photo != null) {
			if(pc.photo.id == NO_ID)
			{
				add(pc.photo);
			}
			else
			{
				update(pc.photo);
			}
			
		}
		
		printDebug("Update PC", results);

		return (results.length > 0 && results[0].count > 0);
	}

	public long add(DataElement data) throws RemoteException,
			OperationApplicationException {		
		
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
		ContentProviderOperation.Builder builder = ContentProviderOperation
				.newInsert(Data.CONTENT_URI)
				.withValue(Data.RAW_CONTACT_ID, data.personContactId)
				.withValue(Data.MIMETYPE, data.mimetype);

		Map<String, Object> datas = data.getDataColumns();
		Set set = datas.entrySet();
		Iterator iterator = set.iterator();
		while (iterator.hasNext()) {
			Map.Entry mapEntry = (Map.Entry) iterator.next();
			if (mapEntry.getValue() != null) {
				builder.withValue((String) mapEntry.getKey(),
						mapEntry.getValue());
			}			
		}

		ops.add(builder.build());
		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);
		return (results.length == 1) ? ContentUris.parseId(results[0].uri)
				: INVALID_ID;
	}

	public boolean update(DataElement data) throws RemoteException,
			OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

		ContentProviderOperation.Builder builder = ContentProviderOperation
				.newUpdate(
						ContentUris.withAppendedId(Data.CONTENT_URI, data.id))
				.withValue(Data.RAW_CONTACT_ID, data.personContactId)
				.withValue(Data.MIMETYPE, data.mimetype);

		Map<String, Object> datas = data.getDataColumns();
		Set set = datas.entrySet();
		Iterator iterator = set.iterator();
		while (iterator.hasNext()) {
			Map.Entry mapEntry = (Map.Entry) iterator.next();
			if (mapEntry.getValue() != null) {
				builder.withValue((String) mapEntry.getKey(),
						mapEntry.getValue());
			}
		}
		ops.add(builder.build());

		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);

		printDebug("Update Data", results);

		return (results.length > 0 && results[0].count > 0);
	}

	public boolean delete(Person person) throws RemoteException,
			OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
		ops.add(ContentProviderOperation.newDelete(
				ContentUris.withAppendedId(
						ContactsContract.Contacts.CONTENT_URI, person.id))
				.build());
		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);
		printDebug("DeletePerson", results);
		return (results.length > 0 && results[0].count > 0);
	}

	public boolean delete(PersonContact pc) throws RemoteException,
			OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
		ops.add(ContentProviderOperation.newDelete(
				ContentUris.withAppendedId(RawContacts.CONTENT_URI, pc.id))
				.build());
		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);
		printDebug("DeletePersonContact", results);
		return (results.length > 0 && results[0].count > 0);
	}

	public boolean delete(DataElement data) throws RemoteException,
			OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
		ops.add(ContentProviderOperation.newDelete(
				ContentUris.withAppendedId(Data.CONTENT_URI, data.id)).build());
		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);
		printDebug("DeleteData", results);
		return (results.length > 0 && results[0].count > 0);
	}

	public Iterator<Person> list() {
		String[] projection = { ContactsContract.Contacts._ID,
				ContactsContract.Contacts.DISPLAY_NAME };
		return new PersonIterator(ctx.getContentResolver().query(
				ContactsContract.Contacts.CONTENT_URI, projection, null, null,
				null));
	}

	public Iterator<PersonContact> listContacts() {
		String[] projection = { ContactsContract.RawContacts._ID,
				ContactsContract.RawContacts.CONTACT_ID,
				ContactsContract.RawContacts.ACCOUNT_NAME,
				ContactsContract.RawContacts.ACCOUNT_TYPE };
		PersonContactIterator personIterator = 
		 new PersonContactIterator(ctx.getContentResolver().query(
				ContactsContract.RawContacts.CONTENT_URI, projection, null,
				null, null));
		
		if(!personIterator.isHasData())
			return null;
		return personIterator;
	}

	public boolean updateData(long id, Map<String, String> params)
			throws RemoteException, OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
		ContentProviderOperation.Builder builder = ContentProviderOperation
				.newUpdate(ContentUris.withAppendedId(
						ContactsContract.Data.CONTENT_URI, id));
		for (Iterator<String> it = params.keySet().iterator(); it.hasNext();) {
			String key = it.next();
			builder.withValue(key, params.get(key));
		}
		ops.add(builder.build());
		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);
		printDebug("Update Data", results);
		return (results.length > 0 && results[0].count > 0);
	}

	public boolean updatePersonContact(long id, Map<String, String> params)
			throws RemoteException, OperationApplicationException {
		ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
		ContentProviderOperation.Builder builder = ContentProviderOperation
				.newUpdate(ContentUris.withAppendedId(
						ContactsContract.RawContacts.CONTENT_URI, id));
		for (Iterator<String> it = params.keySet().iterator(); it.hasNext();) {
			String key = it.next();
			builder.withValue(key, params.get(key));
		}
		ops.add(builder.build());
		ContentProviderResult[] results = ctx.getContentResolver().applyBatch(
				ContactsContract.AUTHORITY, ops);
		printDebug("Update Data", results);
		return (results.length > 0 && results[0].count > 0);
	}

	public Person getPersonById(long id) {
		String[] projection = { ContactsContract.Contacts._ID,
				ContactsContract.Contacts.DISPLAY_NAME };
		Cursor c = ctx.getContentResolver().query(
				ContentUris.withAppendedId(
						ContactsContract.Contacts.CONTENT_URI, id), projection,
				null, null, null);
		if (!c.moveToFirst())
			return null;
		Person p = Person.recoverPerson(c.getLong(0), c.getString(1));
		c.close();
		return p;
	}

	public PersonContact getPersonContactById(long id) {
		String[] projection = { RawContacts._ID, RawContacts.CONTACT_ID,
				RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE };
		Cursor c = ctx.getContentResolver().query(
				ContentUris.withAppendedId(RawContacts.CONTENT_URI, id),
				projection, null, null, null);
		if (!c.moveToFirst())
			return null;
		PersonContact pc = PersonContact.recoverPersonContact(c.getLong(0),
				c.getLong(1), c.getString(2), c.getString(3));
		
		c.close();
		return pc;
	}

	private void printDebug(String tag, ContentProviderResult[] results) {
		for (ContentProviderResult r : results) {
			Log.d(TAG, r.toString());
			if (r.uri != null)
				Log.d(TAG, "URI=" + r.uri.toString());
		}
	}

	public Iterator<Person> complexSearch(Map<String, String> params) {
		Cursor[] cursors = new Cursor[params.size()];
		int i = 0;
		for (Iterator<String> it = params.keySet().iterator(); it.hasNext();) {
			String uri = it.next();
			String value = params.get(uri);
			cursors[i++] = m_fullSearch(uri, value);
		}
		return new PersonIterator(CursorUtils.intersect(cursors));
	}

	public Iterator<Person> fullSearch(String dataType, String value) {
		return new PersonIterator(m_fullSearch(dataType, value));
	}

	private Cursor m_fullSearch(String dataType, String value) {
		String[] projection = { Data.CONTACT_ID, Data.DISPLAY_NAME };
		String selection = Data.DATA1 + "=?" + " AND " + Data.MIMETYPE + "=?";
		String[] selectArgs = { value, dataType };

		return ctx.getContentResolver().query(Data.CONTENT_URI, projection,
				selection, selectArgs, null);
	}

	public Iterator<Person> filterSearch(Uri filterUri, String value) {
		return new PersonIterator(m_filterSearch(filterUri, value));
	}

	private Cursor m_filterSearch(Uri filterUri, String value) {
		String[] projection = { Data.CONTACT_ID, Data.DISPLAY_NAME };
		Uri uri = Uri.withAppendedPath(filterUri, Uri.encode(value));
		return ctx.getContentResolver()
				.query(uri, projection, null, null, null);
	}

	public Iterator<Person> telephoneSearch(String number) {
		return new PersonIterator(m_telephoneSearch(number));
	}

	private Cursor m_telephoneSearch(String number) {
		String[] projection = { Person._ID, Data.DISPLAY_NAME };
		Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
				Uri.encode(number));
		return ctx.getContentResolver()
				.query(uri, projection, null, null, null);
	}
}
